var bignum = require('bignum');
var promise = require('promise');
var merklebitcoin = promise.denodeify(require('merkle-bitcoin'));
var util = require('./util.js');
var transactions = require('./zTransactions.js');

function calcRoot(hashes) {
    return Object.values(merklebitcoin(hashes))[2].root;
}

var merkle = {
    getRoot: function (rpcData, generateTxRaw) {
        hashes = [util.reverseBuffer(Buffer.from(generateTxRaw, 'hex')).toString('hex')];
        rpcData.transactions.forEach(function (value) {
            // Segwit support
            if (value.txid !== undefined) {
                hashes.push(value.txid);
            } else {
                hashes.push(value.hash);
            }
        });
        if (hashes.length === 1) {
            return hashes[0];
        }
        return calcRoot(hashes);
    }
};

/**
 * The BlockTemplate class holds a single job.
 * and provides several methods to validate and submit it to the daemon coin
**/
var BlockTemplate = module.exports = function BlockTemplate(jobId, rpcData, extraNoncePlaceholder, recipients, poolAddress, coinbase, coin) {
    //private members
    var submits = [];

    //public members
    this.rpcData = rpcData;
    this.jobId = jobId;

    // get target info
    this.target = bignum(rpcData.target, 16);

    this.difficulty = parseFloat((evdiff1 / this.target.toNumber()).toFixed(9));

    // generate the fees and coinbase tx
    let blockReward = {
        'total': (this.rpcData.miner) * (coin.subsidyMultipleOfSatoshi || 100000000)
    };

    var masternodeReward;
    var masternodePayee;
    var masternodePayment;
    var zelnodeBasicAddress;
    var zelnodeBasicAmount;
    var zelnodeSuperAddress;
    var zelnodeSuperAmount;
    var zelnodeBamfAddress;
    var zelnodeBamfAmount;

    if (coin.payFoundersReward === true) {
        if (!this.rpcData.founders || this.rpcData.founders.length <= 0) {
            console.log('Error, founders reward missing for block template!');
        } else if (coin.payAllFounders){
            // SafeCash / Genx
            if (!rpcData.masternode_payments_started)
            {
                // Pre masternodes
                blockReward = {
                    "miner": (this.rpcData.miner),
                    "infrastructure": (this.rpcData.infrastructure),
                    "giveaways": (this.rpcData.giveaways),
                    "founderSplit": (this.rpcData.loki),
                    "total": (this.rpcData.miner + this.rpcData.founderstotal + this.rpcData.infrastructure + this.rpcData.giveaways)
                };
            } else {
                // Masternodes active
                blockReward = {
                    "miner": (this.rpcData.miner),
                    "infrastructure": (this.rpcData.infrastructure),
                    "giveaways": (this.rpcData.giveaways),
                    "founderamount": (this.rpcData.founderamount),
                    "total": (this.rpcData.coinbasevalue)
                };
            }
        } else {
            blockReward = {
                "total": (this.rpcData.miner + this.rpcData.founders + (this.rpcData.treasury || 0) + this.rpcData.securenodes + this.rpcData.supernodes) * 100000000
            };
        }
    } 

    //Vidulum VRS Support
    if(coin.VRSEnabled === true) {
        //VRS Activation is Live
        if(this.rpcData.height >= coin.VRSBlock){
            if (!this.rpcData.vrsReward || this.rpcData.vrsReward.length <= 0) {
                console.log('Error, vidulum reward system payout missing for block template!');
            }
            else {
                blockReward = {
                    "total": (this.rpcData.miner * 100000000) + this.rpcData.vrsReward + this.rpcData.payee_amount
                };
            }
        }
        else{ //VRS Ready but not yet activated by chain
            blockReward = {
                "total": (this.rpcData.miner * 100000000) + this.rpcData.payee_amount
            };
        }
    }

    masternodeReward = rpcData.payee_amount;
    masternodePayee = rpcData.payee;
    masternodePayment = rpcData.masternode_payments;
    zelnodeBasicAddress = coin.payZelNodes ? rpcData.basic_zelnode_address : null;
    zelnodeBasicAmount = coin.payZelNodes ? (rpcData.basic_zelnode_payout || 0) : 0;
    zelnodeSuperAddress = coin.payZelNodes ? rpcData.super_zelnode_address : null;
    zelnodeSuperAmount = coin.payZelNodes ? (rpcData.super_zelnode_payout || 0) : 0;
    zelnodeBamfAddress = coin.payZelNodes ? rpcData.bamf_zelnode_address : null;
    zelnodeBamfAmount = coin.payZelNodes ? (rpcData.bamf_zelnode_payout || 0): 0;

    var fees = [];
    rpcData.transactions.forEach(function(value) {
        fees.push(value);
    });
    this.rewardFees = transactions.getFees(fees);
    rpcData.rewardFees = this.rewardFees;

    if (typeof this.genTx === 'undefined') {
        this.genTx = transactions.createGeneration(
            rpcData,
            blockReward,
            this.rewardFees,
            recipients,
            poolAddress,
            coinbase,
            coin,
            masternodeReward,
            masternodePayee,
            masternodePayment,
            zelnodeBasicAddress,
            zelnodeBasicAmount,
            zelnodeSuperAddress,
            zelnodeSuperAmount,
            zelnodeBamfAddress,
            zelnodeBamfAmount
        ).toString('hex');
        this.genTxHash = transactions.txHash();
    }

    // generate the merkle root
    this.prevHashReversed = util.reverseBuffer(Buffer.from(rpcData.previousblockhash, 'hex')).toString('hex');
    if (rpcData.finalsaplingroothash) {
        this.hashReserved = util.reverseBuffer(Buffer.from(rpcData.finalsaplingroothash, 'hex')).toString('hex');
    } else {
        this.hashReserved = '0000000000000000000000000000000000000000000000000000000000000000'; //hashReserved
    }
    this.merkleRoot = merkle.getRoot(rpcData, this.genTxHash);
    this.txCount = this.rpcData.transactions.length + 1; // add total txs and new coinbase
    this.merkleRootReversed = util.reverseBuffer(Buffer.from(this.merkleRoot, 'hex')).toString('hex');
    // we can't do anything else until we have a submission

    //block header per https://github.com/zcash/zips/blob/master/protocol/protocol.pdf
    this.serializeHeader = function(nTime, nonce){
        var header =  Buffer.alloc(140);
        var position = 0;

        header.writeUInt32LE(this.rpcData.version, position += 0, 4, 'hex');
        header.write(this.prevHashReversed, position += 4, 32, 'hex');
        header.write(this.merkleRootReversed, position += 32, 32, 'hex');
        header.write(this.hashReserved, position += 32, 32, 'hex');
        header.write(nTime, position += 32, 4, 'hex');
        header.write(util.reverseBuffer(Buffer.from(rpcData.bits, 'hex')).toString('hex'), position += 4, 4, 'hex');
        header.write(nonce, position += 4, 32, 'hex');
        return header;
    };

    // join the header and txs together
    this.serializeBlock = function(header, soln){

        var txCount = this.txCount.toString(16);
        if (Math.abs(txCount.length % 2) == 1) {
          txCount = "0" + txCount;
        }

        if (this.txCount <= 0x7f){
            var varInt = Buffer.from(txCount, 'hex');
        } else if (this.txCount <= 0x7fff) {
            if (txCount.length == 2) {
                txCount = "00" + txCount;
            }
            var varInt = Buffer.concat([Buffer.from('FD', 'hex'), util.reverseBuffer(Buffer.from(txCount, 'hex'))]);
        }

        buf = Buffer.concat([
            header,
            soln,
            varInt,
            Buffer.from(this.genTx, 'hex')
        ]);

        if (this.rpcData.transactions.length > 0) {
            this.rpcData.transactions.forEach(function (value) {
                tmpBuf = Buffer.concat([buf, Buffer.from(value.data, 'hex')]);
                buf = tmpBuf;
            });
        }

        return buf;
    };

    // submit the block header
    this.registerSubmit = function(header, soln){
        var submission = (header + soln).toLowerCase();
        if (submits.indexOf(submission) === -1){

            submits.push(submission);
            return true;
        }
        return false;
    };

    this.getOdoKey = function(){
        if (this.rpcData && this.rpcData.odokey !== undefined) {
            return this.rpcData.odokey;
        }
        return null;
    };

    // used for mining.notify
    this.getJobParams = function(){
        if (!this.jobParams){
            this.jobParams = [
                this.jobId,
                util.packUInt32LE(this.rpcData.version).toString('hex'),
                this.prevHashReversed,
                this.merkleRootReversed,
                this.hashReserved,
                util.packUInt32LE(rpcData.curtime).toString('hex'),
                util.reverseBuffer(Buffer.from(this.rpcData.bits, 'hex')).toString('hex'),
                true
            ];

            // VerusHash V2.1 activation
            if (this.rpcData.solution !== undefined && typeof this.rpcData.solution === "string") {
                // trim trailing 0's
                let reservedSolutionSpace = this.rpcData.solution.replace(/[0]+$/, "");
                if ((reservedSolutionSpace.length % 2) == 1) {
                    reservedSolutionSpace += "0";
                }
                this.jobParams.push(reservedSolutionSpace);
            }
        }
        return this.jobParams;
    };
};
